package org.wheel.base.utils;

import java.lang.invoke.MethodHandles;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.function.Function;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.util.ClassUtils;
import org.springframework.util.SystemPropertyUtils;

/**
 * @author klaus.wang
 */
public class ReflectionUtils extends org.springframework.util.ReflectionUtils {

	protected static final Logger logger = LoggerFactory.getLogger(ReflectionUtils.class);

	private static final Class<?> Table = forName("javax.persistence.Table");

	private static final Class<?> Column = forName("javax.persistence.Column");

	private static final Class<?> Id = forName("javax.persistence.Id");

	/**
	 * 获取泛型指向类型
	 *
	 * @param cls
	 * @param pos
	 * @return
	 */
	public static Class<?> getType(Class<?> cls, int pos) {
		ParameterizedType parameterizedType = (ParameterizedType) (cls.isInterface() ? cls.getGenericInterfaces()[0]
				: cls.getGenericSuperclass());
		Type[] types = parameterizedType.getActualTypeArguments();
		if (types != null && pos < types.length) {
			return (Class<?>) types[pos];
		}
		return null;
	}

	private static Map<Class<?>, Map<String, Object>> dataModelMap = new HashMap<>();

	public static Map<String, Object> getClassDataModel(Class<?> e) {
		return dataModelMap.computeIfAbsent(e, ReflectionUtils::getDataModel);
	}

	static Map<String, Object> getDataModel(Class<?> e) {
		String NAME = "name";
		Object table = getAnnotation(e, Table);
		if (table == null) {
			return null;
		}
		Object name = invokeMethod(table, NAME, null);
		if (name == null || StringUtils.isBlank(String.valueOf(name))) {
			return null;
		}
		List<Object> columns = new ArrayList<>();
		Map<String, Object> dataModel = new HashMap<>();

		ReflectionUtils.doWithFields(e, (field) -> {

			Object column = getAnnotation(field, Column);
			if (column == null) {
				return;
			}
			Map<String, Object> model = new HashMap<>();
			model.put(NAME, field.getName());

			Object columnName = invokeMethod(column, NAME, null);

			model.put("columnName", columnName);
			model.put("type", field.getType().getSimpleName());

			if (getAnnotation(field, Id) != null) {
				dataModel.put("idColumn", new HashMap<>(model));
				model.put("isIdColumn", true);
			}
			columns.add(model);
		}, (field) -> true);

		dataModel.put("tableName", name);
		dataModel.put("tn", name);
		dataModel.put(NAME, e.getSimpleName());
		dataModel.put("package", e.getPackage().getName());
		dataModel.put("columns", columns);

		return dataModel;
	}

	private static Class<?> forName(String string) {
		try {
			return Class.forName(string);
		} catch (Exception ex) {
//	            logger.error(ex.getMessage());
			// ignore
		}
		return null;
	}

	private static Object getAnnotation(Object ins, Object... objects) {
		return invokeMethod(ins, "getAnnotation", new Class[] { Class.class }, objects);
	}

	private static Object invokeMethod(Object ins, String name, Class<?>[] pts, Object... objects) {
		try {
			return ReflectionUtils.invokeMethod(ins.getClass().getMethod(name, pts), ins, objects);
		} catch (Exception e) {

		}
		return null;
	}

	/**
	 * 扫描包下的class类
	 * 
	 * @param packages
	 * @param func
	 */
	public static void scanPackages(String packages, Function<Class<?>, Boolean> func) {

		scanPackages(packages, "/*.class", (metadataReaderFactory, resource) -> {
			String fullClassName = loadClassName(metadataReaderFactory, resource);
			if (StringUtils.isBlank(fullClassName)) {
				return true;
			}
			try {
				if (!func.apply(Class.forName(fullClassName))) {
					return false;
				}
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
			}
			return true;
		});

//		// 获取Spring资源解析器
//		ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
//
//		// 创建Spring中用来读取resource为class的工具类
//		MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resourcePatternResolver);
//
//		String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
//				+ ClassUtils.convertClassNameToResourcePath(SystemPropertyUtils.resolvePlaceholders(packages))
//				+ "/*.class";
//
//		try {
//			// 获取packageSearchPath下的Resource，这里得到的Resource是Class信息
//			Resource[] resources = resourcePatternResolver.getResources(packageSearchPath);
//			for (Resource resource : resources) {
//				String fullClassName = loadClassName(metadataReaderFactory, resource);
//				if (StringUtils.isBlank(fullClassName)) {
//					continue;
//				}
//
//				if (!func.apply(Class.forName(fullClassName))) {
//					break;
//				}
//
//			}
//		} catch (Exception e) {
//			logger.error(e.getMessage());
//		}
	}

	public static void scanPackages(String packages, String suffix,
			BiFunction<MetadataReaderFactory, Resource, Boolean> func) {

		// 获取Spring资源解析器
		ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();

		// 创建Spring中用来读取resource为class的工具类
		MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resourcePatternResolver);

		String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
				+ ClassUtils.convertClassNameToResourcePath(SystemPropertyUtils.resolvePlaceholders(packages)) + suffix;

		try {
			// 获取packageSearchPath下的Resource，这里得到的Resource是Class信息
			Resource[] resources = resourcePatternResolver.getResources(packageSearchPath);
			for (Resource resource : resources) {
				if (!func.apply(metadataReaderFactory, resource)) {
					break;
				}
			}
		} catch (Exception e) {
			logger.error(e.getMessage());
		}
	}

	/**
	 * 加载资源，根据resource获取className
	 *
	 * @param metadataReaderFactory spring中用来读取resource为class的工具
	 * @param resource              这里的资源就是一个Class
	 */
	private static String loadClassName(MetadataReaderFactory metadataReaderFactory, Resource resource) {
		try {
			if (resource.isReadable()) {
				MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);
				if (metadataReader != null) {
					return metadataReader.getClassMetadata().getClassName();
				}
			}
		} catch (Exception e) {
			logger.error(e.getMessage());
		}
		return null;
	}

	/**
	 * 处理默认方法
	 * 
	 * @param proxy
	 * @param method
	 * @param args
	 * @return
	 * @throws Throwable
	 */
	public static Object invokeDefaultMethod(Object proxy, Method method, Object[] args) throws Throwable {
		final Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class
				.getDeclaredConstructor(Class.class, int.class);
		if (!constructor.isAccessible()) {
			constructor.setAccessible(true);
		}
		final Class<?> declaringClass = method.getDeclaringClass();
		return constructor
				.newInstance(declaringClass,
						MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED | MethodHandles.Lookup.PACKAGE
								| MethodHandles.Lookup.PUBLIC)
				.unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);
	}

	/**
	 * Backport of java.lang.reflect.Method#isDefault()
	 */
	public static boolean isDefaultMethod(Method method) {
		return (method.getModifiers() & (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC
				&& method.getDeclaringClass().isInterface();
	}

	public static Object accessibleFieldValue(Object value, String fieldName) {
		Field field = ReflectionUtils.findField(value.getClass(), fieldName);
		ReflectionUtils.makeAccessible(field);
		return getField(field, value);
	}

	public static void main(String[] args) {

	}

}
